package cn.itsource.service.impl;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.itsource.api.CustomerApi;
import cn.itsource.api.DriverPointApi;
import cn.itsource.constants.Constants;
import cn.itsource.exception.GlobalException;
import cn.itsource.pojo.bo.*;
import cn.itsource.pojo.domain.ChargeRuleReturn;
import cn.itsource.pojo.domain.ChargeRuleStart;
import cn.itsource.pojo.domain.Order;
import cn.itsource.mapper.OrderMapper;
import cn.itsource.pojo.domain.OrderBill;
import cn.itsource.pojo.dto.OrderCreateDto;
import cn.itsource.pojo.result.DriverPointResult;
import cn.itsource.pojo.vo.ExecuteOrderInfoForCustomerVO;
import cn.itsource.pojo.vo.ExecuteOrderInfoForDriverVO;
import cn.itsource.result.JSONResult;
import cn.itsource.service.*;
import cn.itsource.utils.AssertUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import lombok.extern.slf4j.Slf4j;

import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import static cn.hutool.core.date.DateUtil.*;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author ????
 * @since 2024-06-29
 */
@Service
@Slf4j
@Transactional
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    @Autowired
    private CustomerApi customerFineApi;

    @Autowired
    private IChargeRuleStartService ruleStartService;

    @Autowired
    private IChargeRuleReturnService ruleReturnService;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private IOrderBillService billService;

    @Autowired
    private RocketMQTemplate mqTemplate;

    @Autowired
    private DefaultMQPullConsumer defaultMQPullConsumer;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private IChargeRuleWaitService ruleWaitService;

    @Autowired
    private DriverPointApi driverPointApi;

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 1.先进行参数判断，条件判断，查看是否有进行的订单，是否有未交罚款
     * 2.计算费用
     * 3.保存订单
     * 4.司机匹配
     * 5.推送订单
     *
     * @param dto
     * @return
     */
    @Override
    public String create(OrderCreateDto dto) {
        //参数判断，条件判断：
        Long loginId = StpUtil.getLoginIdAsLong();
        // 是否有进行中的订单
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getCustomerId, loginId)
                .in(Order::getStatus, Constants.Order.ORDER_STATUS_IN_PROCESS_CUSTOMER));
        AssertUtil.isNull(order,"已有订单");

        //乘客是否有罚款未交
        JSONResult<Boolean> jsonResult = customerFineApi.hasFine(loginId);
        AssertUtil.isTrue(jsonResult.isSuccess(),"下单失败");

        Boolean hasFine = jsonResult.getData();
        AssertUtil.isFalse(hasFine,"请先交罚款");

        //1.计算费用
        OrderFeeCalResult calResult = calculateFee(dto.getExpectsMileage());
        //2.保存订单
        order = saveOrder(dto,calResult.getTotalAmount());
        saveBill(order,calResult);

        //3.司机匹配: 3个司机
        List<GeoSearchResult> driverGeoResults = findDriver(Double.valueOf(order.getStartPlaceLongitude()),Double.valueOf(order.getStartPlaceLatitude()));
        AssertUtil.isNotEmpty(driverGeoResults,"暂时没有司机接单");
        boolean matchSuccess = false;

        //4.司机接单条件过滤：setting - redis
        for (GeoSearchResult geoSearchResult : driverGeoResults) {
            //司机的setting  -  司机的ID
            String key = String.format(Constants.Redis.DRIVER_SETTING,geoSearchResult.getMember());
            DriverSettingBo settingBO = (DriverSettingBo) redisTemplate.opsForValue().get(key);
            if(settingBO == null){ continue ;}

            //过滤接单条件 : 接单条件 距离 > 实际距离
            if(Double.valueOf(settingBO.getRangeDistance()).compareTo(geoSearchResult.getDistance()) < 0
                    || Double.valueOf(settingBO.getOrderDistance()).compareTo(order.getExpectsMileage().doubleValue()) < 0
            ){  continue; }

            matchSuccess = true;

            // 保存派单表  ：订单ID，司机ID；
            sendOrderToDriver(order,geoSearchResult);
        }
        AssertUtil.isTrue(matchSuccess,"没有符合接单条件的司机");

        //有司机下单成功就把订单缓存进Redis
        String format = String.format(Constants.Redis.KEY_PREFIX_ORDER, order.getOrderNo());
        redisTemplate.opsForValue().set(format, order);
        return order.getOrderNo();
    }

    //司机拉取订单
    @Override
    public List<OrderToDriver> pullForCurrentDriver() {
        //司机的ID
        Long loginId = Long.valueOf(StpUtil.getLoginId().toString());

        try {
            //订阅消息队列，指定topic
            Set<MessageQueue> messageQueues = defaultMQPullConsumer.fetchSubscribeMessageQueues(Constants.MQ.TOPIC_ORDER_DRIVER);
            //封装结果
            List<OrderToDriver> result = new ArrayList<>();

            for (MessageQueue messageQueue : messageQueues) {

                //通过偏移量来获取消息 ：当前队列的消费位置
                long offset = defaultMQPullConsumer.fetchConsumeOffset(messageQueue, true);
                log.info("拉取消息的偏移量 {}-{}",messageQueue.getQueueId(),offset);

                //拉取消息，需要指定tags拼接司机ID， 来区分司机:
                String tags = String.format(Constants.MQ.TAGS_ORDER_TOD_DRIVER, loginId);
                PullResult pullResult = defaultMQPullConsumer.pull(messageQueue, tags, offset, 32);

                //找到消息
                if (pullResult != null && pullResult.getPullStatus().equals(PullStatus.FOUND)) {
                    //拿到消息列表
                    List<MessageExt> messageExtList = pullResult.getMsgFoundList();

                    if(messageExtList == null || messageExtList.size() == 0)continue;

                    for (MessageExt messageExt : messageExtList) {
                        //拿到消息内容，转换为订单对象
                        String message = new String(messageExt.getBody(), StandardCharsets.UTF_8);
                        OrderToDriver orderMesssage = JSON.parseObject(message, OrderToDriver.class);

                        //添加到结果列表
                        result.add(orderMesssage);
                        log.info("获取订单消息 {}",orderMesssage);
                    }
                }

                if(pullResult != null){
                    //重要：修改消息的消费位置，如果位置不后移，消息会一直被消费
                    defaultMQPullConsumer.updateConsumeOffset(messageQueue,pullResult.getNextBeginOffset());
                }

            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            throw new GlobalException("订单拉取失败");
        }
    }


    /**
     * 取消订单
     * 1.先根据订单号，查询对应订单
     * 2.判断订单是不是：待接单状态
     * 3.订单的乘客ID必须是登录人的ID
     * 4.修改状态为：已取消
     * @param orderNo 订单号
     */
    @Override
    public void autoCancel(String orderNo) {
        //1.根据订单号，查询订单
        AssertUtil.isNotEmpty(orderNo,"非法请求");
        //2.判断订单是不是：待接单状态
        //3.订单的乘客ID必须是登录人的ID
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getStatus,Constants.Order.ORDER_STATUS_WAIT)
                .eq(Order::getCustomerId, StpUtil.getLoginIdAsLong())
                .eq(Order::getOrderNo, orderNo));

        AssertUtil.isNotNull(order,"非法请求");

        //4.修改状态为：已取消
        order.setStatus(Constants.Order.ORDER_STATUS_CUSTOMER_CANCEL_ORDER);
        order.setUpdateTime(new Date());
        this.updateById(order);
    }

    /**
     * 抢单
     * 1.先进行参数判断
     * 2.加分布式锁
     * 3.去Redis中获取订单
     * 4.获取的订单必须是待接单状态
     * 5.接单以后修改订单：设置订单状态：为已接单 ， 以及将司机信息，和接单时间等 - 更新到数据库
     * 6.删除Redis中对应的订单
     * @param orderNo ：订单号
     */
    @Override
    public void kill(String orderNo) {
        //1.先进行参数判断
        AssertUtil.isNotEmpty(orderNo,"非法请求");

        //2.加分布式锁,锁颗粒度越小越好
        String lockName = String.format(Constants.Redis.LOCK_ORDER, orderNo);
        RLock lock = redissonClient.getLock(lockName);
        try {
            lock.lock();
            log.info("加锁成功 {}",lockName);

            //3.去Redis中获取订单
            String key = String.format(Constants.Redis.KEY_PREFIX_ORDER,orderNo);
            Order order = (Order) redisTemplate.opsForValue().get(key);

            AssertUtil.isNotNull(order,"订单被抢走了");
            //4.获取的订单必须是待接单状态
            AssertUtil.isEquals(order.getStatus(),Constants.Order.ORDER_STATUS_WAIT,"状态异常");

            //5.接单以后修改订单：设置订单状态：为已接单 ， 以及将司机信息，和接单时间等 - 更新到数据库
            order.setStatus(Constants.Order.ORDER_STATUS_ACCEPTED);
            Date now = new Date();
            order.setAcceptTime(now);
            order.setUpdateTime(now) ;
            Long loginId = StpUtil.getLoginIdAsLong();
            order.setDriverId(loginId);

            //司机的信息
            LoginInfoToRedis loginInfo = (LoginInfoToRedis) redisTemplate.opsForValue().get(String.format(Constants.Redis.KEY_PREFIX_LOGIN_INFO, loginId));
            order.setDriverName(loginInfo.getName());
            order.setDriverPhoto(loginInfo.getAvatar());
            order.setDriverPhone(loginInfo.getPhone());
            super.updateById(order);

            // 6.删除Redis中对应的订单
            redisTemplate.delete(key);

        }finally {
            if(lock.isLocked()){
                lock.unlock();
                log.info("解锁成功 {}",lockName);
            }
        }
    }

    @Override
    public Boolean isAccpet(String orderNo) {
        // 查询是否有被接单
        AssertUtil.isNotEmpty(orderNo,"非法请求参数");
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
               .eq(Order::getOrderNo, orderNo)
               .eq(Order::getStatus, Constants.Order.ORDER_STATUS_ACCEPTED));
        return order != null;
    }

    /**
     * 加载乘客的订单
     * @return
     */
    @Override
    public ExecuteOrderInfoForCustomerVO pullInprocessForCurrentCustomer() {
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getCustomerId,StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.Order.ORDER_STATUS_IN_PROCESS_CUSTOMER));
                if (order == null)
        return null;
                return ExecuteOrderInfoForCustomerVO.builder()
                        .orderNo(order.getOrderNo())
                        .status(order.getStatus())
                        .build();
    }

    /**
     * 加载司机的订单
     * @return
     */
    @Override
    public ExecuteOrderInfoForDriverVO pullInprocessForCurrentDriver() {
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getDriverId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.Order.ORDER_STATUS_IN_PROCESS_DRIVER));
        if(order == null)return null;

        ExecuteOrderInfoForDriverVO orderVo = new ExecuteOrderInfoForDriverVO();
        orderVo.setOrderNo(order.getOrderNo());
        orderVo.setCustomerPhoto(order.getCustomerPhoto());
        orderVo.setCustomerName(order.getCustomerName() == null ? order.getCustomerPhone() : order.getCustomerName());

        orderVo.setCustomerPhone(order.getCustomerPhone());
        orderVo.setStartPlace(order.getStartPlace());
        orderVo.setStartLongitude(order.getStartPlaceLongitude());
        orderVo.setStartLatitude(order.getStartPlaceLatitude());
        orderVo.setEndPlace(order.getEndPlace());
        orderVo.setEndLongitude(order.getEndPlaceLongitude());
        orderVo.setEndLatitude(order.getEndPlaceLatiude());
        orderVo.setFavourFee(order.getFavourAmount());
        orderVo.setCarPlate(order.getCarPlate());
        orderVo.setCarType(order.getCarType());
        orderVo.setCreateTime(order.getCreateTime());
        orderVo.setStatus(order.getStatus());
        return orderVo;
    }

    /**
     * 到代驾点
     * @param orderNo 订单号
     */
    @Override
    public void arrive(String orderNo) {
        //2.1参数判断，拿到订单判断状态，必须是已接单
        AssertUtil.isNotEmpty(orderNo,"非法请求");
        //根据订单号查询订单
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getDriverId,StpUtil.getLoginIdAsLong())
                .eq(Order::getStatus,Constants.Order.ORDER_STATUS_ACCEPTED)
                .eq(Order::getOrderNo,orderNo));
        AssertUtil.isNotNull(order,"订单异常");
        //2.2修改订单状态；已到达，设置到达时间，修改时间
        order.setStatus(Constants.Order.ORDER_STATUS_ARRIVE);
        order.setArriveTime(new Date());
        order.setUpdateTime(new Date());
        super.updateById(order);





    }

    /**
     * 开始代驾
     * 1.先进行参数判断，拿到订单的判断状态，必须是已到达才能进行
     * 2.修改订单的状态变为开始代驾，设置开始代驾的时间，修改时间
     * 3.计算等待的时间，和等时费；（总共时长-免费的分钟）* 1
     * @param orderNo 订单号
     */
    @Override
    public void start(String orderNo) {
        //1.先进行参数判断，拿到订单的判断状态，必须是已到达才能进行
        AssertUtil.isNotEmpty(orderNo,"非法参数请求");
        //根据订单号查询订单
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getDriverId,StpUtil.getLoginIdAsLong())
                .eq(Order::getStatus,Constants.Order.ORDER_STATUS_ARRIVE)
                .eq(Order::getOrderNo,orderNo));
        AssertUtil.isNotNull(order,"订单异常");
        //1.2修改订单的状态变为开始代驾，设置开始代驾的时间，修改时间
        Date date = new Date();
        order.setStartTime(date);
        order.setStatus(Constants.Order.ORDER_STATUS_START_DRIVING);
        order.setUpdateTime(date);
        //2.修改订单的状态变为开始代驾，设置开始代驾的时间，修改时间
        Long diffMin = DateUtil.between(order.getArriveTime(), date, DateUnit.MINUTE);
        //根据订单号查询OrderBill
        OrderBill bill = billService.getOne(new LambdaQueryWrapper<OrderBill>()
                .eq(OrderBill::getOrderNo, orderNo));
        bill.setWaitingMinute(diffMin.intValue());
        //计算等时费
        WaitingFeeCalResult calResult = ruleWaitService.cal(diffMin.intValue());

        //3.计算等待的时间，和等时费；（总共时长-免费的分钟）* 1
        order.setExpectsOrderAmount(order.getExpectsOrderAmount().add(calResult.getWaitingAmount()));
        super.updateById(order);

        //等时费
        bill.setWaitingAmount(calResult.getWaitingAmount());
        bill.setUpdateTime(date);
        bill.setWaitingMinute(diffMin.intValue());
        bill.setFreeBaseWaitingMinute(calResult.getFreeBaseWaitingMinute());
        bill.setExeceedBaseWaitingEveryKmAmount(calResult.getExeceedBaseWaitingEveryKmAmount());
        bill.setExeceedBaseWaitingMinute(BigDecimal.valueOf(calResult.getExeceedBaseWaitingMinute()));

        billService.updateById(bill);




    }

    /**
     * 结束代驾
     * 1.先进行参数判断，拿到订单判断状态，必须是开始代驾的
     * 2.修改订单状态，结束代驾，设置结束代驾的时间，修改时间
     * 3.计算实际里程？实际里程费，实际的往返里程.返程费
     * @param orderNo 订单号
     */
    @Override
    public void endDriving(String orderNo) {
        //1.先进行参数判断，拿到订单判断状态，必须是开始代驾的
        AssertUtil.isNotEmpty(orderNo,"非法请求参数");
        //根据订单号查询订单
        Order order = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getDriverId, StpUtil.getLoginIdAsLong())
                .eq(Order::getStatus, Constants.Order.ORDER_STATUS_START_DRIVING)
                .eq(Order::getOrderNo, orderNo));
        AssertUtil.isNotNull(order,"订单异常");
        //2.修改订单状态，结束代驾，设置结束代驾的时间，修改时间
        Date date = new Date();
        order.setEndTime(date);
        order.setStatus(Constants.Order.ORDER_STATUS_COMPLETE_DRIVED);
        order.setUpdateTime(date);
        //3.计算实际里程？实际里程费，实际的往返里程.返程费
        JSONResult<List<DriverPointResult>> listJSONResult = driverPointApi.selectByOrderNo(orderNo);
        AssertUtil.isTrue(listJSONResult.isSuccess(),"坐标获取失败");
        List<DriverPointResult> resultList = listJSONResult.getData();

        //计算真实里程
        BigDecimal realMileage = calRealMileage(resultList);
        log.info("计算真实里程{}",realMileage);

        //计算价格：里程和返程
        OrderFeeCalResult orderFeeCalResult = calculateFee(realMileage);
        order.setReturnMileage(realMileage);

        //查询byid
        OrderBill byId = billService.getById(order.getId());

        //预估 = 里程费 + 返程费 + 等时费
        order.setExpectsOrderAmount(orderFeeCalResult.getTotalAmount().add(byId.getWaitingAmount()));
        //order.setRealOrderAmount(orderFeeCalResult.getTotalAmount());
        super.updateById(order);

        //起步里程
        byId.setBaseMileage(orderFeeCalResult.getBaseMileage());
        //起步价
        byId.setBaseMileageAmount(orderFeeCalResult.getBaseMileageAmount());
        //每公里价格
        byId.setExceedBaseMileageEveryKmAmount(orderFeeCalResult.getExceedBaseMileageEveryKmAmount());
        //超出的里程 ：里程 - 起步里程
        byId.setExceedBaseMileage(orderFeeCalResult.getExceedBaseMileage());
        //超出的费用：（里程 - 起步里程） * 每公里价格
        byId.setExceedBaseMileageAmount(orderFeeCalResult.getExceedBaseMileageAmount());
        //里程费：起步价 + 超出的费用
        byId.setMileageAmount(orderFeeCalResult.getMileageAmount());
        //返程里程 = 订单里程
        byId.setReturnMileage(orderFeeCalResult.getReturnMileage());
        //免费返程里程
        byId.setFreeBaseReturnMileage(orderFeeCalResult.getFreeBaseReturnMileage());
        //返程费每千米价格
        byId.setExceedBaseReturnEveryKmAmount(orderFeeCalResult.getExceedBaseReturnEveryKmAmount());
        //超出免费返程的里程：里程 - 免费里程
        byId.setExceedFreeBaseReturnMileage(orderFeeCalResult.getExceedFreeBaseReturnMileage());
        //返程费 = （里程 - 免费里程） * 每公里价格
        byId.setReturnAmont(orderFeeCalResult.getReturnAmont());

        //账单的真实金额 ：在确认费用的时候设置
        //bill.setRealOrderAmount(orderFeeCalResult.getTotalAmount());

        billService.updateById(byId);

    }



    //发送订单数据给司机
    private void sendOrderToDriver(Order order,GeoSearchResult geoSearchResult){
        // 保存派单表  ：订单ID，司机ID；
        //6.推送订单：发送派单消息到MQ，每个司机一个消息，司机的ID作为消息的Tags进行区分
        String destination = Constants.MQ.TOPIC_ORDER_DRIVER + ":" + String.format(Constants.MQ.TAGS_ORDER_TOD_DRIVER, geoSearchResult.getMember());

        OrderToDriver orderToDriver = OrderToDriver.builder()
                .orderNo(order.getOrderNo())
                .mileage(order.getExpectsMileage())
                .expectsAmount(order.getExpectsOrderAmount())
                .distance(BigDecimal.valueOf(geoSearchResult.getDistance()))
                .favourAmount(order.getFavourAmount())
                .from(order.getStartPlace())
                .to(order.getEndPlace())
                .build();
        //订单消息
        SendResult sendResult = mqTemplate.syncSend(destination, MessageBuilder.withPayload(orderToDriver).build());//目的地和消息的对象
        log.info("发送订单数据给MQ：{}",sendResult);
    }


    //范围内搜索司机
    private List<GeoSearchResult> findDriver(double longitude , double latitude  ) {

        //构建中心点
        Point point = new Point(longitude, latitude);
        //设置半径范围 (KILOMETERS 千米；METERS 米)
        Metric metric = RedisGeoCommands.DistanceUnit.KILOMETERS;
        Distance distance = new Distance(50, metric);
        Circle circle = new Circle(point, distance);
        //设置参数 包括距离、坐标、条数
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                .includeDistance()      //包含距离
                .includeCoordinates()   //包含经纬度
                .sortAscending()        //正序排序
                .limit(10);      //条数

        GeoResults<RedisGeoCommands.GeoLocation<Object>> geoResults = redisTemplate.opsForGeo().radius(
                Constants.Redis.DRIVER_GEO, //司机坐标GEO的KEY,
                circle, //搜索的中心点
                geoRadiusCommandArgs);//搜索的参数

        List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> contents = geoResults.getContent();

        List<GeoSearchResult> results = new ArrayList<>(contents.size());

        for (GeoResult<RedisGeoCommands.GeoLocation<Object>> geoResult : contents) {
            //到中心点的距离
            double resultDistance = geoResult.getDistance().getValue();
            //司机ID
            Object resultName = geoResult.getContent().getName();

            log.info("搜索到附近的司机 {} - {} - {}",resultName,resultDistance);

            results.add(GeoSearchResult.builder()
                    .member(resultName)
                    .distance(resultDistance).build());
        }
        return results;

    }

    /**
     * 创建账单
     */
    private void saveBill(Order order,OrderFeeCalResult calResult) {
        OrderBill bill = new OrderBill();
        bill.setId(order.getId());
        bill.setOrderId(order.getId());
        //好处费
        bill.setFavourAmount(new BigDecimal("0"));
        //订单描述
        bill.setOrderDetail(order.getRemark());

        //起步里程
        bill.setBaseMileage(calResult.getBaseMileage());
        //起步价
        bill.setBaseMileageAmount(calResult.getBaseMileageAmount());
        //每公里价格
        bill.setExceedBaseMileageEveryKmAmount(calResult.getExceedBaseMileageEveryKmAmount());
        //超出的里程 ：里程 - 起步里程
        bill.setExceedBaseMileage(calResult.getExceedBaseMileage());
        //超出的费用：（里程 - 起步里程） * 每公里价格
        bill.setExceedBaseMileageAmount(calResult.getExceedBaseMileageAmount());
        //里程费：起步价 + 超出的费用
        bill.setMileageAmount(calResult.getMileageAmount());
        //返程里程 = 订单里程
        bill.setReturnMileage(calResult.getReturnMileage());
        //免费返程里程
        bill.setFreeBaseReturnMileage(calResult.getFreeBaseReturnMileage());
        //返程费每千米价格
        bill.setExceedBaseReturnEveryKmAmount(calResult.getExceedBaseReturnEveryKmAmount());
        //超出免费返程的里程：里程 - 免费里程
        bill.setExceedFreeBaseReturnMileage(calResult.getExceedFreeBaseReturnMileage());
        //返程费 = （里程 - 免费里程） * 每公里价格
        bill.setReturnAmont(calResult.getReturnAmont());

        bill.setOrderNo(order.getOrderNo());
        bill.setCreateTime(new Date());
        billService.save(bill);
    }
    /**
     * 创建订单
     */
    private Order saveOrder(OrderCreateDto dto,BigDecimal orderAmount){
        Order order = new Order();
        BeanUtils.copyProperties(dto,order);
        //订单编号
        order.setOrderNo(IdUtil.createSnowflake(1L,1L).nextIdStr());
        order.setCustomerId(StpUtil.getLoginIdAsLong());
        //预估-返程里程
        order.setReturnMileage(dto.getExpectsMileage());

        //预估订单金额
        order.setExpectsOrderAmount(orderAmount);
        //顾客小费
        order.setFavourAmount(new BigDecimal("0"));
        //系统奖励费
        order.setIncentiveAmount(new BigDecimal("0"));
        order.setCreateTime(new Date());
        //状态：待接单
        order.setStatus(Constants.Order.ORDER_STATUS_WAIT);
        //订单的描述
        order.setRemark("乘客"+StpUtil.getLoginIdAsLong()+"下单，预估费用："+orderAmount);
        //乘客信息 // 登录的时候 -> 把用户信息存入Redis -> 从Redis取
        LoginInfoToRedis loginInfo = (LoginInfoToRedis) redisTemplate.opsForValue()
                .get(String.format(Constants.Redis.KEY_PREFIX_LOGIN_INFO, StpUtil.getLoginIdAsLong()));
        order.setCustomerName(loginInfo.getName());
        order.setCustomerPhone(loginInfo.getPhone());
        order.setCustomerPhoto(loginInfo.getAvatar());
        super.save(order);

        return order;
    }

    /**
     *
     *  1.获取起步价规则：根据下单时间的小时，去范围匹配起步价规则
     *  2.里程费 =  起步价 + (里程 - 基础里程) * 每公里价格
     *  3.返程费 = （里程 - 免费返程里程） * 每公里价格
     */
    private OrderFeeCalResult calculateFee(BigDecimal mileage){
        //*  1.拿到起步价规则：根据下单时间的小时，去范围匹配起步价规则
        int hour = hour(new Date(), true);

        ChargeRuleStart ruleStart = ruleStartService.getOne(new LambdaQueryWrapper<ChargeRuleStart>()
                .le(ChargeRuleStart::getHourStart, hour)
                .ge(ChargeRuleStart::getHourEnd, hour));
        AssertUtil.isNotNull(ruleStart,"未找到计价规则");

        OrderFeeCalResult result = new OrderFeeCalResult();

        //*  2.里程费 =  起步价 + (里程 - 基础里程) * 每公里价格
        BigDecimal mileageAmount = ruleStart.getAmount();
        if(mileage.compareTo(ruleStart.getBaseMileage()) > 0){
            //超出里程
            BigDecimal exceedMileage = mileage.subtract(ruleStart.getBaseMileage());
            result.setExceedBaseMileage(exceedMileage);

            //超出价格
            BigDecimal exceedAmount = exceedMileage.multiply(ruleStart.getExceedEveryKmAmount());
            result.setExceedBaseMileageAmount(exceedAmount);

            //里程费
            mileageAmount = mileageAmount.add(exceedAmount);
        }
        log.info("里程费 {}",mileageAmount);

        //*  3.返程费 = （里程 - 免费返程里程） * 每公里价格
        ChargeRuleReturn ruleReturn = ruleReturnService.getOne(null);
        AssertUtil.isNotNull(ruleReturn,"返程规则未找到");

        BigDecimal returnAmount = BigDecimal.ZERO;
        if(mileage.compareTo(ruleReturn.getFreeBaseReturnMileage()) > 0) {
            //超出免费返程部分的里程
            BigDecimal exceedFreeMileage = mileage.subtract(ruleReturn.getFreeBaseReturnMileage());
            result.setExceedFreeBaseReturnMileage(exceedFreeMileage);

            //返程费(超出公里*每公里价格)
            returnAmount = exceedFreeMileage.multiply(ruleReturn.getExceedEveryKmAmount());
        }
        log.info("返程费 {}",returnAmount);

        //起步里程
        result.setBaseMileage(ruleStart.getBaseMileage());
        //起步价
        result.setBaseMileageAmount(ruleStart.getAmount());
        //每公里价格
        result.setExceedBaseMileageEveryKmAmount(ruleStart.getExceedEveryKmAmount());
        //里程费
        result.setMileageAmount(mileageAmount);
        //返程里程
        result.setReturnMileage(mileage);
        //免费返程里程
        result.setFreeBaseReturnMileage(ruleReturn.getFreeBaseReturnMileage());
        //返程费每公里价格
        result.setExceedBaseReturnEveryKmAmount(ruleReturn.getExceedEveryKmAmount());
        //返程费
        result.setReturnAmont(returnAmount);

        return result;
    }

    //分段测距
    public BigDecimal calRealMileage(List<DriverPointResult> pointResults){

        BigDecimal totalDistance = new BigDecimal("0");

        for (int i = 0; i < pointResults.size() - 1; i++) {
            DriverPointResult curr = pointResults.get(i);
            DriverPointResult next = pointResults.get(i + 1);
            //分段测距
            BigDecimal distance = calLocationMileage(curr.getLongitude(), curr.getLatitude(), next.getLongitude(), next.getLatitude());
            totalDistance = totalDistance.add(distance);
        }
        return totalDistance;
    }
    //分段测距
    public BigDecimal calLocationMileage(String fromLong , String fromLat ,String toLong , String toLat){
        String url = "https://apis.map.qq.com/ws/distance/v1/matrix/?mode=driving&from=%s&to=%s&key=%s";
        String from = fromLat+","+fromLong;
        String to = toLat+","+toLong;
        url = String.format(url, from, to, "DVFBZ-5TK37-MBOXB-HFUXD-UTYC3-DMBED");
        //发送请求
        ResponseEntity<JSONObject> responseEntity = restTemplate.getForEntity(url, JSONObject.class);
        AssertUtil.isTrue(responseEntity.getStatusCode() == HttpStatus.OK,"测距失败");

        JSONObject resBody = responseEntity.getBody();
        JSONObject result = resBody.getJSONObject("result");
        JSONArray rows = result.getJSONArray("rows");
        if(rows == null || rows.size() == 0){
            throw new GlobalException("测距失败");
        }
        JSONObject row = rows.getJSONObject(0);
        JSONArray elements = row.getJSONArray("elements");
        JSONObject element = elements.getJSONObject(0);
        Integer distance = element.getInteger("distance");
        //千米
        return BigDecimal.valueOf(distance).divide(BigDecimal.valueOf(1000), 2, BigDecimal.ROUND_HALF_UP);
    }

}
